use std::{borrow::Borrow, cell::RefCell, fmt::Arguments, rc::Rc};

use super::{
    callable::{Callable, SyntaxCallable},
    environment::Environment,
    expr::*,
    nativeFunction::NativeClock,
    object::Object,
    statements::*,
    syntaxResult::SyntaxResult,
    syntaxFunction::SyntaxFunction,
    token::SyntaxKind,
};

pub struct Interpreter {
    pub globals: Rc<RefCell<Environment>>,
    environment: RefCell<Rc<RefCell<Environment>>>,
    nest: RefCell<usize>,
}
#[allow(non_snake_case)]
impl StmtVisit<()> for Interpreter {
    fn visitExpressionStmt(&self, expr: &ExpressionStmt) -> Result<(), SyntaxResult> {
        self.Evaluate(&expr.expression)?;
        Ok(())
    }

    fn visitPrinterStmt(&self, expr: &PrinterStmt) -> Result<(), SyntaxResult> {
        let value = self.Evaluate(&expr.expression)?;
        //println!("Calculation results:{value}");
        println!(" ={value}");
        Ok(())
    }

    fn visitVariableStmt(&self, expr: &VariableDeclarationStmt) -> Result<(), SyntaxResult> {
        let value = if let Some(init) = &expr.initializer {
            self.Evaluate(init)?
        } else {
            Object::Nil
        };
        //println!("visitVariableStmt:{}",expr.name.as_string());
        self.environment
            .borrow()
            .borrow_mut()
            .define(expr.name.as_string(), value);
        Ok(())
    }

    fn visitBlockStmt(&self, expr: &BlockStmt) -> Result<(), SyntaxResult> {
        let e = Environment::newWithEnclosing(self.environment.borrow().clone());
        self.executeBlock(&expr.statements, e)
    }

    fn visitIfStmt(&self, expr: &IfStmts) -> Result<(), SyntaxResult> {
        if self.isTruthy(&self.Evaluate(&expr.condition)?) {
            self.execute(&expr.thenBranch)?;
        } else if let Some(el) = &expr.elseBranch {
            self.execute(el)?;
        }
        Ok(())
    }

    fn visitWhileStmt(&self, expr: &WhileStmts) -> Result<(), SyntaxResult> {
        *self.nest.borrow_mut() += 1;

        while self.isTruthy(&self.Evaluate(&expr.condition)?) {
            match self.execute(&expr.body) {
                Err(SyntaxResult::Break) => break,
                Err(e) => return Err(e),
                _ => {}
            }
        }
        *self.nest.borrow_mut() -= 1;
        Ok(())
    }

    fn visitFunctionStmt(&self, expr: &FunctionStmts) -> Result<(), SyntaxResult> {
        let function = SyntaxFunction::new(expr);
        self.environment.borrow().borrow_mut().define(
            expr.name.as_string(),
            Object::Function(Callable {
                func: Rc::new(function),
            }),
        );

        Ok(())
    }
    fn visitBreakStmt(&self, expr: &BreakStmt) -> Result<(), SyntaxResult> {
        if *self.nest.borrow() == 0 {
            return Err(SyntaxResult::RuntimeError(
                &expr.token,
                "break outside of while/for loop.",
            ));
        }
        Err(SyntaxResult::Break)
    }

    fn visitReturnStmt(&self, expr: &ReturnStmt) -> Result<(), SyntaxResult> {
        
        if let Some(value) = &expr.value {
            return Err(SyntaxResult::ReturnValue(self.Evaluate(value)?));
        }
        Err(SyntaxResult::ReturnValue(Object::Nil))
    }
}

#[allow(non_snake_case)]
impl Interpreter {
    pub fn new() -> Self {
        let mut globals = Rc::new(RefCell::new(Environment::new()));
        globals.borrow_mut().define(
            &"clock".to_string(),
            Object::Function(Callable {
                func: Rc::new(NativeClock {}),
            }),
        );

        Self {
            globals: Rc::clone(&globals),
            environment: RefCell::new(Rc::clone(&globals)),
            nest: RefCell::new(0),
        }
    }
    fn Evaluate(&self, expr: &SyntaxExpression) -> Result<Object, SyntaxResult> {
        expr.accept::<Object>(self)
    }

    fn isTruthy(&self, obj: &Object) -> bool {
        !matches!(obj, Object::Bool(false) | Object::Nil)
    }
    fn execute(&self, stmt: &Stmt) -> Result<(), SyntaxResult> {
        stmt.accept(self)
    }
    pub fn executeBlock(&self, statements: &[Stmt], _env: Environment) -> Result<(), SyntaxResult> {
        let previous = self.environment.replace(Rc::new(RefCell::new(_env)));
        let result = statements
            .iter()
            .try_for_each(|statement| self.execute(statement));
        self.environment.replace(previous);
        result
    }
    pub fn interpret(&self, stmts: &[Stmt]) -> bool {
        let mut success = true;

        *self.nest.borrow_mut() = 0;

        for stmt in stmts {
            if let Err(e) = self.execute(stmt) {
                //e.report();

                success = false;
            }
        }
        success
    }
    pub fn PrintEnvironment(&self) {
        println!("{:?}", self.environment.borrow().borrow());
    }
}
#[allow(non_snake_case)]
impl ExpressionVisitor<Object> for Interpreter {
    fn visitBinaryExpression(&self, expr: &BinaryExpression) -> Result<Object, SyntaxResult> {
        let mut left = self.Evaluate(&expr.left)?;
        let mut right = self.Evaluate(&expr.right)?;

        let result = match expr.operator.kind {
            SyntaxKind::Plus => left + right,
            SyntaxKind::Minus => left - right,
            SyntaxKind::Asterisk => left * right,
            SyntaxKind::Slash => left / right,
            SyntaxKind::Percent => left % right,

            SyntaxKind::Greater => Object::Bool(left > right),
            SyntaxKind::GreaterEquals => Object::Bool(left >= right),
            SyntaxKind::Less => Object::Bool(left < right),
            SyntaxKind::LessEquals => Object::Bool(left <= right),
            SyntaxKind::Equals => Object::Bool(left == right),
            SyntaxKind::BangEquals => Object::Bool(left != right),

            _ => {
                return Err(SyntaxResult::RuntimeError(
                    &expr.operator,
                    "visitBinaryExpression: error operator",
                ));
            }
        };
        Ok(result)
    }

    fn visitGroupingExpression(&self, expr: &GroupingExpression) -> Result<Object, SyntaxResult> {
        self.Evaluate(&expr.expression)
    }

    fn visitLiteralExpression(&self, expr: &LiteralExpression) -> Result<Object, SyntaxResult> {
        Ok(expr.value.clone().unwrap())
    }

    fn visitUnaryExpression(&self, expr: &UnaryExpression) -> Result<Object, SyntaxResult> {
        let right = self.Evaluate(&expr.right)?;
        match expr.operator.kind {
            SyntaxKind::Minus => match right {
                Object::Number(num) => Ok(Object::Number(-num)),
                _ => Ok(Object::Nil),
            },
            SyntaxKind::Plus => match right {
                Object::Number(num) => Ok(Object::Number(num)),
                _ => Ok(Object::Nil),
            },
            SyntaxKind::Bang => Ok(Object::Bool(self.isTruthy(&right))),
            _ => Ok(Object::Nil),
        }
    }

    fn visitProgramExpression(&self, expr: &ProgramExpression) -> Result<Object, SyntaxResult> {
        todo!()
    }

    fn visitVariableExpression(&self, expr: &VariableExpression) -> Result<Object, SyntaxResult> {
        self.environment.borrow().borrow_mut().get(&expr.name)
        //Ok(Object::Nil)
    }

    fn visitAssignmentExpression(
        &self,
        expr: &AssignmentExpression,
    ) -> Result<Object, SyntaxResult> {
        let mut value = self.Evaluate(&expr.value)?;

        self.environment
            .borrow()
            .borrow_mut()
            .assign(&expr.name, value.clone())?;

        Ok(value)
    }

    fn visitLogicalExpression(&self, expr: &LogicalExpression) -> Result<Object, SyntaxResult> {
        let left = self.Evaluate(&expr.left)?;
        if expr.operator.is(SyntaxKind::Or) {
            if self.isTruthy(&left) {
                return Ok(left);
            }
        } else {
            // SyntaxKind::And;
            if !self.isTruthy(&left) {
                return Ok(left);
            }
        }
        self.Evaluate(&expr.right)
    }

    fn visitCallExpression(&self, expr: &CallExpression) -> Result<Object, SyntaxResult> {
        let _callee = self.Evaluate(&expr.callee)?;

        let mut _arguments = Vec::new();
        for _argument in &expr.arguments {
            _arguments.push(self.Evaluate(_argument)?);
        }
        if let Object::Function(function) = _callee {
            if _arguments.len() != function.func.arity() {
                return Err(SyntaxResult::RuntimeError(
                    &expr.paren,
                    &format!(
                        "Expect '{}' arguments but got '{}' .",
                        function.func.arity(),
                        _arguments.len()
                    ),
                ));
            }
            return function.func.Call(self, _arguments);
        }

        Err(SyntaxResult::RuntimeError(
            &expr.paren,
            "Can only call function and classes.",
        ))
    }
}

#[cfg(test)]
#[allow(non_snake_case)]
mod tests {
    use crate::Syntax::{
        expr::*,
        interpreter::Interpreter,
        object::Object,
        text::{SourceLocation, TextSpan},
        token::{SyntaxKind, SyntaxToken},
    };

    fn makeLiteralNumber(val: f64) -> Box<SyntaxExpression> {
        Box::new(SyntaxExpression::Literal(LiteralExpression {
            value: Some(Object::Number(val)),
        }))
    }
    fn makeLiteralString(val: &str) -> Box<SyntaxExpression> {
        Box::new(SyntaxExpression::Literal(LiteralExpression {
            value: Some(Object::String(val.to_string())),
        }))
    }
    fn makeLiteralNil() -> Box<SyntaxExpression> {
        Box::new(SyntaxExpression::Literal(LiteralExpression {
            value: Some(Object::Nil),
        }))
    }

    #[test]
    fn test_binary_add() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralNumber(12.0),
            operator: SyntaxToken::new(
                SyntaxKind::Plus,
                TextSpan::new(0, 1, "+".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralNumber(127.0),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Number(139.0)));
    }

    #[test]
    fn test_binary_sub() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralNumber(12.0),
            operator: SyntaxToken::new(
                SyntaxKind::Minus,
                TextSpan::new(0, 1, "-".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralNumber(127.0),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Number(-115.0)));
    }
    #[test]
    fn test_binary_mul() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralNumber(12.0),
            operator: SyntaxToken::new(
                SyntaxKind::Asterisk,
                TextSpan::new(0, 1, "*".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralNumber(10.0),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Number(120.0)));
    }
    #[test]
    fn test_binary_div() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralNumber(125.0),
            operator: SyntaxToken::new(
                SyntaxKind::Slash,
                TextSpan::new(0, 1, "/".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralNumber(5.0),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Number(25.0)));
    }
    #[test]
    fn test_unary_minus() {
        let eval = Interpreter::new();
        let expr = UnaryExpression {
            operator: SyntaxToken::new(
                SyntaxKind::Minus,
                TextSpan::new(0, 1, "-".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralNumber(127.0),
        };
        let result = eval.visitUnaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Number(-127.0)));
    }
    #[test]
    fn test_binary_Rem() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralNumber(125.0),
            operator: SyntaxToken::new(
                SyntaxKind::Percent,
                TextSpan::new(0, 1, "%".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralNumber(5.0),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Number(0.0)));
    }
    #[test]
    fn test_unary_minus_minus() {
        let eval = Interpreter::new();
        let expr = UnaryExpression {
            operator: SyntaxToken::new(
                SyntaxKind::Minus,
                TextSpan::new(0, 1, "+".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralNumber(-127.0),
        };
        let result = eval.visitUnaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Number(127.0)));
    }

    #[test]
    fn test_binary_string() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralString("hello,"),
            operator: SyntaxToken::new(
                SyntaxKind::Plus,
                TextSpan::new(0, 1, "+".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralString("world!"),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(
            result.ok(),
            Some(Object::String("hello,world!".to_string()))
        );
    }
    fn makeLiteralbool(val: bool) -> Box<SyntaxExpression> {
        Box::new(SyntaxExpression::Literal(LiteralExpression {
            value: Some(Object::Bool(val)),
        }))
    }
    #[test]
    fn test_binary_bool_error() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralbool(false),
            operator: SyntaxToken::new(
                SyntaxKind::Plus,
                TextSpan::new(0, 1, ">".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralbool(true),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Nil));
    }
    #[test]
    fn test_Greater_than_true() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralNumber(19.0),
            operator: SyntaxToken::new(
                SyntaxKind::Greater,
                TextSpan::new(0, 1, ">".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralNumber(10.0),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Bool(true)));
    }
    #[test]
    fn test_Greater_than_false() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralNumber(19.0),
            operator: SyntaxToken::new(
                SyntaxKind::Greater,
                TextSpan::new(0, 1, ">".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralNumber(19.0),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Bool(false)));
    }
    #[test]
    fn test_Bool_Equal_than_false() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralbool(false),
            operator: SyntaxToken::new(
                SyntaxKind::Equals,
                TextSpan::new(0, 1, "==".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralbool(true),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Bool(false)));
    }
    #[test]
    fn test_Bool_Equal_than_true() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralbool(true),
            operator: SyntaxToken::new(
                SyntaxKind::Equals,
                TextSpan::new(0, 1, "==".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralbool(true),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Bool(true)));
    }
    #[test]
    fn test_Equal_String_false() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralString("hello"),
            operator: SyntaxToken::new(
                SyntaxKind::Equals,
                TextSpan::new(0, 1, "!=".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralString("hello!"),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Bool(false)));
    }

    #[test]
    fn test_Equal_Nil() {
        let eval = Interpreter::new();
        let expr = BinaryExpression {
            left: makeLiteralNil(),
            operator: SyntaxToken::new(
                SyntaxKind::Equals,
                TextSpan::new(0, 1, "!=".to_string()),
                SourceLocation { line: 0, column: 0 },
            ),
            right: makeLiteralNil(),
        };
        let result = eval.visitBinaryExpression(&expr);
        assert!(result.is_ok());

        assert_eq!(result.ok(), Some(Object::Bool(true)));
    }
}
